package top.ezzz.search.model;

import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import java.util.Arrays;
import java.util.function.Consumer;

/**
 * @author noear
 * @since 1.0.3
 */
public class EsAggs {

    private final JSONObject oNode;

    private JSONObject lastLevel2Node;

    private String oNodeAsField;

    public EsAggs(JSONObject oNode) {
        this.oNode = oNode;
    }


    private JSONObject getLevel2Node(String name) {
        oNodeAsField = name;
        if (oNode.get(name) == null) {
            oNode.set(name, JSONUtil.createObj());
        }
        lastLevel2Node = oNode.getJSONObject(name);
        return lastLevel2Node;
    }

    private void funSet(String asField, String field, String funName) {
        if (getLevel2Node(asField).get(funName) == null) {
            getLevel2Node(asField).set(funName, JSONUtil.createObj());
        }
        getLevel2Node(asField).getJSONObject(funName).set("field", field);
    }

    public EsAggs asField(String asField) {
        MapUtil.renameKey(oNode, oNodeAsField, asField);
        return this;
    }

    //
    //============ Metrics =============
    //

    /**
     * sum，求合
     */
    public EsAggs sum(String field) {
        funSet(field + "_sum", field, "sum");
        return this;
    }

    /**
     * sum，求合（支持别名）
     */
    public EsAggs sum(String field, String asFiled) {
        funSet(asFiled, field, "sum");
        return this;
    }

    /**
     * avg，求平均值
     */
    public EsAggs avg(String field) {
        funSet(field + "_avg", field, "avg");
        return this;
    }

    /**
     * avg，求平均值（支持别名）
     */
    public EsAggs avg(String field, String asField) {
        funSet(asField, field, "avg");
        return this;
    }

    /**
     * max，求最大值
     */
    public EsAggs max(String field) {
        funSet(field + "_max", field, "max");
        return this;
    }

    /**
     * max，求最大值（支持别名）
     */
    public EsAggs max(String field, String asField) {
        funSet(asField, field, "max");
        return this;
    }

    /**
     * min，求最小值
     */
    public EsAggs min(String field) {
        funSet(field + "_min", field, "min");
        return this;
    }

    /**
     * min，求最小值（支持别名）
     */
    public EsAggs min(String field, String asField) {
        funSet(asField, field, "min");
        return this;
    }

    /**
     * count，值计数
     */
    public EsAggs count(String field) {
        funSet(field + "_count", field, "value_count");
        return this;
    }

    /**
     * count，值计数（支持别名）
     */
    public EsAggs count(String field, String asField) {
        funSet(asField, field, "value_count");
        return this;
    }


    /**
     * top_hits，每一个聚合Bucket里面仅返回指定顺序的前N条数据。
     */
    public EsAggs topHits(int size) {
        return topHits(size, null);
    }

    public EsAggs topHits(int size, Consumer<EsSort> sort) {
        if (oNode.get("$topHits") == null) {
            oNode.set("$topHits", JSONUtil.createObj());
        }
        JSONObject $topHits = oNode.getJSONObject("$topHits");
        if ($topHits.get("top_hits") == null) {
            $topHits.set("top_hits", JSONUtil.createObj());
        }
        JSONObject top_hits = $topHits.getJSONObject("top_hits");
        top_hits.set("size", size);
        if (sort != null) {
            if (top_hits.get("sort") == null) {
                top_hits.set("sort", JSONUtil.createArray());
            }
            EsSort s = new EsSort(top_hits.getJSONArray("sort"));
            sort.accept(s);
        }
        return this;
    }

    /**
     * cardinality，先去重再计数
     */
    public EsAggs cardinality(String field) {
        funSet(field + "_cardinality", field, "cardinality");
        return this;
    }

    /**
     * cardinality，先去重再计数（支持别名）
     */
    public EsAggs cardinality(String field, String asField) {
        funSet(asField, field, "cardinality");
        return this;
    }

    /**
     * percentiles，多值聚合求百分比
     */
    public EsAggs percentiles(String field, Number[] percents) {
        JSONObject level2Node = getLevel2Node(field + "_percentiles");
        if (level2Node.get("percentiles") == null) {
            level2Node.set("percentiles", JSONUtil.createObj());
        }
        JSONObject oNode1 = level2Node.getJSONObject("percentiles");
        oNode1.set("field", field);
        if (oNode1.get("percents") == null) {
            oNode1.set("percents", JSONUtil.createArray());
        }
        oNode1.getJSONArray("percents").addAll(Arrays.asList(percents));
        return this;
    }

    /**
     * percentiles，多值聚合求百分比（支持别名）
     */
    public EsAggs percentiles(String field, String asField, Number[] percents) {
        JSONObject level2Node = getLevel2Node(asField);
        if (level2Node.get("percentiles") == null) {
            level2Node.set("percentiles", JSONUtil.createObj());
        }
        JSONObject oNode1 = level2Node.getJSONObject("percentiles");
        oNode1.set("field", field);
        if (oNode1.get("percents") == null) {
            oNode1.set("percents", JSONUtil.createArray());
        }
        oNode1.getJSONArray("percents").addAll(Arrays.asList(percents));
        return this;
    }

    /**
     * percentiles rank
     */
    public EsAggs percentilesRank(String field, Number[] values) {
        JSONObject level2Node = getLevel2Node(field + "_percentilesRank");
        if (level2Node.get("percentile_ranks") == null) {
            level2Node.set("percentile_ranks", JSONUtil.createObj());
        }
        JSONObject oNode1 = level2Node.getJSONObject("percentile_ranks");
        oNode1.set("field", field);
        if (oNode1.get("values") == null) {
            oNode1.set("values", JSONUtil.createArray());
        }
        oNode1.getJSONArray("values").addAll(Arrays.asList(values));
        return this;
    }

    /**
     * percentiles rank（支持别名）
     */
    public EsAggs percentilesRank(String field, String asField, Number[] values) {
        JSONObject level2Node = getLevel2Node(asField);
        if (level2Node.get("percentile_ranks") == null) {
            level2Node.set("percentile_ranks", JSONUtil.createObj());
        }
        JSONObject oNode1 = level2Node.getJSONObject("percentile_ranks");
        oNode1.set("field", field);
        if (oNode1.get("values") == null) {
            oNode1.set("values", JSONUtil.createArray());
        }
        oNode1.getJSONArray("values").addAll(Arrays.asList(values));
        return this;
    }

    /**
     * extended_stats
     */
    public EsAggs extendedStats(String field) {
        funSet(field + "_extendedStats", field, "extended_stats");
        return this;
    }

    /**
     * extended_stats（支持别名）
     */
    public EsAggs extendedStats(String field, String asField) {
        funSet(asField, field, "extended_stats");
        return this;
    }

    /**
     * stats
     */
    public EsAggs stats(String field) {
        funSet(field + "_stats", field, "stats");
        return this;
    }

    /**
     * stats（支持别名）
     */
    public EsAggs stats(String field, String asField) {
        funSet(asField, field, "stats");
        return this;
    }

    //
    //============ Bucket =============
    //

    /**
     * filter，聚合
     */
    public EsAggs filter(Consumer<EsCondition> condition) {
        if (getLevel2Node("$filter").get("filter") == null) {
            getLevel2Node("$filter").set("filter", JSONUtil.createObj());
        }
        EsCondition c = new EsCondition(getLevel2Node("$filter").getJSONObject("filter"));
        condition.accept(c);
        return this;
    }

    /**
     * range，聚合
     */
    public EsAggs range(String field, Consumer<EsRanges> ranges) {
        JSONObject level2Node = getLevel2Node(field + "_terms");
        if (level2Node.get("range") == null) {
            level2Node.set("range", JSONUtil.createObj());
        }
        JSONObject oNode1 = level2Node.getJSONObject("range");
        oNode1.set("field", field);
        if (oNode1.get("ranges") == null) {
            oNode1.set("ranges", JSONUtil.createArray());
        }
        EsRanges t = new EsRanges(oNode1.getJSONArray("ranges"));
        ranges.accept(t);
        return this;
    }

    /**
     * range，聚合
     */
    public EsAggs range(String field, String asField, Consumer<EsRanges> ranges) {
        if (getLevel2Node(asField).get("terms") == null) {
            getLevel2Node(asField).set("terms", JSONUtil.createObj());
        }
        JSONObject oNode1 = getLevel2Node(asField).getJSONObject("range");
        oNode1.set("field", field);
        if (oNode1.get("ranges") == null) {
            oNode1.set("ranges", JSONUtil.createArray());
        }
        EsRanges t = new EsRanges(oNode1.getJSONArray("ranges"));
        ranges.accept(t);
        return this;
    }

    /**
     * terms，聚合
     */
    public EsAggs terms(String field) {
        terms(field, field + "_terms");
        return this;
    }

    /**
     * terms，聚合
     */
    public EsAggs terms(String field, String asField) {
        terms(field, asField, null);
        return this;
    }


    public EsAggs terms(String field, Consumer<EsTerms> terms) {
        if (getLevel2Node(field + "_terms").get("terms") == null) {
            getLevel2Node(field + "_terms").set("terms", JSONUtil.createObj());
        }
        JSONObject oNode1 = getLevel2Node(field + "_terms").getJSONObject("terms");
        if (field.startsWith("$")) {
            oNode1.set("script", field.substring(1));
        } else {
            oNode1.set("field", field);
        }
        if (terms != null) {
            EsTerms t = new EsTerms(oNode1);
            terms.accept(t);
        }
        return this;
    }

    public EsAggs terms(String field, String asField, Consumer<EsTerms> terms) {
        if (getLevel2Node(asField).get("terms") == null) {
            getLevel2Node(asField).set("terms", new JSONObject());
        }
        JSONObject oNode1 = getLevel2Node(asField).getJSONObject("terms");
        if (field.startsWith("$")) {
            oNode1.set("script", field.substring(1));
        } else {
            oNode1.set("field", field);
        }
        if (terms != null) {
            EsTerms t = new EsTerms(oNode1);
            terms.accept(t);
        }
        return this;
    }


    /**
     * 添加下级条件
     */
    public EsAggs aggs(Consumer<EsAggs> aggs) {
        if (lastLevel2Node == null) {
            throw new IllegalArgumentException("There are no secondary nodes");
        }
        if (lastLevel2Node.get("aggs") == null) {
            lastLevel2Node.set("aggs", JSONUtil.createObj());
        }
        EsAggs c = new EsAggs(lastLevel2Node.getJSONObject("aggs"));
        aggs.accept(c);
        return this;
    }
}